package com.lupengning.payment.service.impl;

import com.google.gson.Gson;
import com.lupengning.payment.config.WxPayConfig;
import com.lupengning.payment.entity.OrderInfo;
import com.lupengning.payment.entity.RefundInfo;
import com.lupengning.payment.enums.OrderStatus;
import com.lupengning.payment.enums.wxpay.WxApiType;
import com.lupengning.payment.enums.wxpay.WxNotifyType;
import com.lupengning.payment.enums.wxpay.WxRefundStatus;
import com.lupengning.payment.enums.wxpay.WxTradeState;
import com.lupengning.payment.service.OrderInfoService;
import com.lupengning.payment.service.PaymentInfoService;
import com.lupengning.payment.service.RefundInfoService;
import com.lupengning.payment.service.WxPayService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

/**
 * @author lupengning
 * @version 1.0
 * @description:
 * @date 2021/12/28 9:20
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

  @Resource
  private RefundInfoService refundsInfoService;

  @Resource
  private WxPayConfig wxPayConfig;

  @Resource
  private CloseableHttpClient wxPayNoSignClient;

  @Resource
  private CloseableHttpClient wxPayClient;

  @Resource
  private OrderInfoService orderInfoService;

  @Resource
  private PaymentInfoService paymentInfoService;

  private final ReentrantLock lock = new ReentrantLock();

  /**
   * 创建订单，调用Native支付接口
   *
   * @param productId
   * @return 返回订单号和二维码链接
   */
  @Override
  public Map<String, Object> nativePay(Long productId) throws Exception {

    log.info("生成订单");
    //存入数据库
    OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);

    String codeUrl = orderInfo.getCodeUrl();
    if (!Objects.isNull(orderInfo) && !StringUtils.isEmpty(codeUrl)) {
      log.info("订单已存在，保存二维码");
      Map<String, Object> map = new HashMap<>();
      map.put("codeUrl", codeUrl);
      map.put("orderNo", orderInfo.getOrderNo());
      return map;
    }

    //调用统一下单Api
    HttpPost httpPost = new HttpPost(
        wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
    //组装请求参数body
    Gson gson = new Gson();
    Map params = new HashMap<String, Object>();
    params.put("appid", wxPayConfig.getAppid());
    params.put("mchid", wxPayConfig.getMchId());
    params.put("description", orderInfo.getTitle());
    params.put("out_trade_no", orderInfo.getOrderNo());
    params.put("notify_url",
        wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

    Map amountMap = new HashMap<String, Object>();
    amountMap.put("total", orderInfo.getTotalFee());
    amountMap.put("currency", "CNY");
    params.put("amount", amountMap);
    String jsonParams = gson.toJson(params);
    log.info("请求参数------->" + jsonParams);

    StringEntity stringEntity = new StringEntity(jsonParams, "UTF-8");
    stringEntity.setContentType("application/json");
    httpPost.setEntity(stringEntity);
    httpPost.setHeader("Accept", "application/json");

    //执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);

    try {
      String bodyAsString = EntityUtils.toString(response.getEntity());
      int statusCode = response.getStatusLine().getStatusCode();
      //处理成功
      if (statusCode == 200) {
        log.info("成功, 返回结果 = " + bodyAsString);
      } else if (statusCode == 204) {
        log.info("成功");
      } else {
        log.info("Native下单失败,响应码 = " + statusCode + ", 返回结果 = " + bodyAsString);
        throw new IOException("request failed");
      }
      //响应结果
      Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
      //二维码
      codeUrl = resultMap.get("code_url");
      orderInfoService.saveCodeUrl(orderInfo.getOrderNo(), codeUrl);
      Map<String, Object> map = new HashMap<>();
      map.put("codeUrl", codeUrl);
      map.put("orderNo", orderInfo.getOrderNo());
      return map;
    } finally {
      response.close();
    }
  }

  @Override
  public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("处理订单");

    Gson gson = new Gson();
    // 密文解密
    String plainText = decryptFromResource(bodyMap);
    HashMap<String, Object> plainTextMap = gson.fromJson(plainText, HashMap.class);

    // 更新订单状态
    String orderNo = (String) plainTextMap.get("out_trade_no");

    // 数据锁
    if (lock.tryLock()) {
      try {
        // 处理重复的通知
        String orderStatus = orderInfoService.getOrderStatus(orderNo);
        if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
          return;
        }

        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);

        // 支付日志
        paymentInfoService.createPaymentInfo(plainText);
      } finally {
        lock.unlock();
      }
    }
  }

  @Override
  public void cancelOrder(String orderNo) throws Exception {
    //调用微信支付的关单接口
    this.closeOrder(orderNo);
//更新商户端的订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);

  }

  /**
   * 查单接口调用
   */
  @Override
  public String queryOrder(String orderNo) throws IOException {
    log.info("查单接口调用 ===> {}", orderNo);
    String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
    url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept", "application/json");
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    try {
      String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
      int statusCode = response.getStatusLine().getStatusCode();//响应状态码
      if (statusCode == 200) { //处理成功
        log.info("成功, 返回结果 =" + bodyAsString);
      } else if (statusCode == 204) { //处理成功，无返回Body
        log.info("成功");
      } else {
        log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
        throw new IOException("request failed");
      }
      return bodyAsString;
    } finally {
      response.close();
    }
  }

  @Override
  /**
   * 根据订单号查询微信支付查单接口，核实订单状态
   * 如果订单已支付，则更新商户端订单状态，并记录支付日志
   * 如果订单未支付，则调用关单接口关闭订单，并更新商户端订单状态
   * @param orderNo
   */
  public void checkOrderStatus(String orderNo) throws Exception {
    log.warn("根据订单号核实订单状态 ===> {}", orderNo);
    //调用微信支付查单接口
    String result = this.queryOrder(orderNo);
    Gson gson = new Gson();
    Map resultMap = gson.fromJson(result, HashMap.class);
    //获取微信支付端的订单状态
    Object tradeState = resultMap.get("trade_state");
    //判断订单状态
    if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
      log.warn("核实订单已支付 ===> {}", orderNo);
      //如果确认订单已支付则更新本地订单状态
      orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
      //记录支付日志
      paymentInfoService.createPaymentInfo(result);
    }
    if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
      log.warn("核实订单未支付 ===> {}", orderNo);
      //如果订单未支付，则调用关单接口
      this.closeOrder(orderNo);
      //更新本地订单状态
      orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void refund(String orderNo, String reason) throws IOException {
    log.info("创建退款单记录");
//根据订单编号创建退款单
    RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
    log.info("调用退款API");
//调用统一下单API
    String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
    HttpPost httpPost = new HttpPost(url);
// 请求body参数
    Gson gson = new Gson();
    Map paramsMap = new HashMap();
    paramsMap.put("out_trade_no", orderNo);//订单编号
    paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
    paramsMap.put("reason", reason);//退款原因
    paramsMap.put("notify_url",
        wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
    Map amountMap = new HashMap();
    amountMap.put("refund", refundsInfo.getRefund());//退款金额
    amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
    amountMap.put("currency", "CNY");//退款币种
    paramsMap.put("amount", amountMap);
//将参数转换成json字符串
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}" + jsonParams);
    StringEntity entity = new StringEntity(jsonParams, "utf-8");
    entity.setContentType("application/json");//设置请求报文格式
    httpPost.setEntity(entity);//将请求报文放入请求对象
    httpPost.setHeader("Accept", "application/json");//设置响应报文格式
//完成签名并执行请求，并完成验签
    CloseableHttpResponse response = wxPayClient.execute(httpPost);
    try {
//解析响应结果
      String bodyAsString = EntityUtils.toString(response.getEntity());
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
        log.info("成功, 退款返回结果 = " + bodyAsString);
      } else if (statusCode == 204) {
        log.info("成功");
      } else {
        throw new RuntimeException("退款异常, 响应码 = " + statusCode + ",退款返回结果 = " + bodyAsString);
      }
//更新订单状态
      orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
      //更新退款单
      refundsInfoService.updateRefund(bodyAsString);
    } finally {
      response.close();
    }
  }

  /**
   * 查询退款接口调用
   *
   * @param refundNo
   * @return
   */
  @Override
  public String queryRefund(String refundNo) throws Exception {
    log.info("查询退款接口调用 ===> {}", refundNo);
    String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
    url = wxPayConfig.getDomain().concat(url);
//创建远程Get 请求对象
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    try {
      String bodyAsString = EntityUtils.toString(response.getEntity());
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
        log.info("成功, 查询退款返回结果 = " + bodyAsString);
      } else if (statusCode == 204) {
        log.info("成功");
      } else {
        throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString);
      }
      return bodyAsString;
    } finally {
      response.close();
    }
  }

  /**
   * 根据退款单号核实退款单状态
   *
   * @param refundNo
   * @return
   */
  @Transactional(rollbackFor = Exception.class)
  @Override
  public void checkRefundStatus(String refundNo) throws Exception {
    log.warn("根据退款单号核实退款单状态 ===> {}"
        , refundNo);
    //调用查询退款单接口
    String result = this.queryRefund(refundNo);
    //组装json请求体字符串
    Gson gson = new Gson();
    Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
    //获取微信支付端退款状态
    String status = resultMap.get("status");
    String orderNo = resultMap.get("out_trade_no");
    if (WxRefundStatus.SUCCESS.getType().equals(status)) {
      log.warn("核实订单已退款成功 ===> {}", refundNo);
      //如果确认退款成功，则更新订单状态
      orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
      //更新退款单
      refundsInfoService.updateRefund(result);
    }
    if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
      log.warn("核实订单退款异常 ===> {}", refundNo);
      //如果确认退款成功，则更新订单状态
      orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
      //更新退款单
      refundsInfoService.updateRefund(result);
    }
  }

  /**
   * 处理退款单
   */
  @Transactional(rollbackFor = Exception.class)
  @Override
  public void processRefund(Map<String, Object> bodyMap) throws Exception {
    log.info("退款单");
    //解密报文
    String plainText = decryptFromResource(bodyMap);
    //将明文转换成map
    Gson gson = new Gson();
    HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String) plainTextMap.get("out_trade_no");
    if (lock.tryLock()) {
      try {
        String orderStatus = orderInfoService.getOrderStatus(orderNo);
        if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
          return;
        }
//更新订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
//更新退款单
        refundsInfoService.updateRefund(plainText);
      } finally {
//要主动释放锁
        lock.unlock();
      }
    }
  }

  @Override
  public String queryBill(String billDate, String type) throws Exception {
    log.warn("申请账单接口调用 {}", billDate);
    String url = "";
    if ("tradebill".equals(type)) {
      url = WxApiType.TRADE_BILLS.getType();
    } else if ("fundflowbill".equals(type)) {
      url = WxApiType.FUND_FLOW_BILLS.getType();
    } else {
      throw new RuntimeException("不支持的账单类型");
    }
    url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
    HttpGet httpGet = new HttpGet(url);
    httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
    CloseableHttpResponse response = wxPayClient.execute(httpGet);
    log.info("response ==============>{}",response);
    try {
      String bodyAsString = EntityUtils.toString(response.getEntity());
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
        log.info("成功, 申请账单返回结果 = " + bodyAsString);
      } else if (statusCode == 204) {
        log.info("成功");
      } else {
        throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
      }
//获取账单下载地址
      Gson gson = new Gson();
      Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
      return resultMap.get("download_url");
    } finally {
      response.close();
    }
  }

  @Override
  public String downloadBill(String billDate, String type) throws Exception {
    log.warn("下载账单接口调用 {}, {}", billDate, type);
//获取账单url地址
    String downloadUrl = this.queryBill(billDate, type);
//创建远程Get 请求对象
    HttpGet httpGet = new HttpGet(downloadUrl);
    httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
    CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);
    try {
      String bodyAsString = EntityUtils.toString(response.getEntity());
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
        log.info("成功, 下载账单返回结果 = " + bodyAsString);
      } else if (statusCode == 204) {
        log.info("成功");
      } else {
        throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ",下载账单返回结果 = " + bodyAsString);
      }
      return bodyAsString;
    } finally {
      response.close();
    }
  }

  private void closeOrder(String orderNo) throws IOException {

    log.info("关单接口的调用，订单号 ===> {}", orderNo);
    //创建远程请求对象
    String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
    url = wxPayConfig.getDomain().concat(url);
    HttpPost httpPost = new HttpPost(url);
    //组装json请求体
    Gson gson = new Gson();
    Map<String, String> paramsMap = new HashMap<>();
    paramsMap.put("mchid", wxPayConfig.getMchId());
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}"
        , jsonParams);
    //将请求参数设置到请求对象中
    StringEntity entity = new StringEntity(jsonParams,
        "utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);
    try {
      int statusCode = response.getStatusLine().getStatusCode();//响应状态码
      if (statusCode == 200) { //处理成功
        log.info("成功200");
      } else if (statusCode == 204) { //处理成功，无返回Body
        log.info("成功204");
      } else {
        log.info("Native下单失败,响应码 =" + statusCode);
        throw new IOException("request failed");
      }
    } finally {
      response.close();
    }
  }


  /**
   * 对称解密
   *
   * @param bodyMap
   * @return
   */
  private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("密文解密");

    // 通知数据
    Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
    // 数据密文
    String ciphertext = resourceMap.get("ciphertext");
    // 随机串
    String nonce = resourceMap.get("nonce");
    // 附件数据
    String associatedData = resourceMap.get("associated_data");

    log.info("数据密文==============>{}", ciphertext);
    AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
        nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
    log.info("明文==============>{}", plainText);
    return plainText;
  }
}
